﻿using Core.Framework.EntityExtend.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Core.Framework.EntityExtend
{
    public static class Exposure
    {
        /// <summary>
        /// 启用查询缓存
        /// [自定义 ICacheService]
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="applicationBuilder"></param>
        /// <returns></returns>
        public static void UseEntityFrameWorkCache<T>(this DbContext context)
            where T : IEntityFrameWorkCacheService, new()
        {
            EFCoreCommon.DefaultCacheService = new T();
        }






        #region BulkEntry

        /// <summary>
        /// 批量删除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context"></param>
        /// <param name="where"></param>
        /// <returns></returns>
        public static void BulkDeleteEntry<T>(this DbContext context, Expression<Func<T, bool>> where)
            where T : class, new()
        {
            var tuple = EFCoreCommon.GetWhereStr<T>(context, where);

            var sql = $"delete {tuple.Item1.tableName} where {tuple.Item2}";


            var mode = new EFCoreTaskParameter.DictionaryParameter
            {
                sql = sql
            };

            if (EFCoreTaskParameter.dictionary.ContainsKey(context))
                EFCoreTaskParameter.dictionary[context].Add(mode);
            else
                EFCoreTaskParameter.dictionary.TryAdd(context, new List<EFCoreTaskParameter.DictionaryParameter>() { mode });
        }

        /// <summary>
        /// 批量修改
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static IResult<T> BulkModifiedEntry<T>(this DbContext context, T entity)
            where T : class, new()
        {
            if (entity == null)
                return new UpdateResult<T>();


            // 字段解析
            var mapping = EFCoreModel<T>.GetModelMapping(context);

            // 构造返回值
            var result = new UpdateResult<T> { context = context };




            var jObject = JObject.Parse("{}");



            // 构造 修改字段
            foreach (PropertyInfo p in entity.GetType().GetProperties())
            {
                if (!p.IsDefined(typeof(NotMappedAttribute), true))
                {
                    var val = p.GetValue(entity);

                    if (null != val)
                    {
                        if (!mapping.rows[p.Name].isIdentity)
                        {
                            if (!val.Equals(0))
                            {
                                result.Fields.Add(new Tuple<string, object, Type>(mapping.rows[p.Name].name, val, p.PropertyType));
                            }
                        }
                    }
                }
            }

            // 修改字段为0 则直接返回
            if (result.Fields.Count == 0)
                return result;

            Type[] BooleanTypes = new Type[] { typeof(bool), typeof(bool?) };

            Type[] DigitalTypes = new Type[]{
                typeof(int), typeof(int?),
                typeof(decimal), typeof(decimal?),
                typeof(double), typeof(double?),
                typeof(long), typeof(long?)
            };

            // 构造 操作数据
            result.sql =
                $@"update {mapping.tableName} " +
                $"set " +
                string.Join(',', result.Fields.Select(a =>
                {
                    if (BooleanTypes.Contains(a.Item3))
                    {
                        jObject[a.Item1] = (bool)a.Item2 == true ? 1 : 0;
                    }
                    else
                    {
                        jObject[a.Item1] = a.Item2.ToString();
                    }
                    return $" {a.Item1} = @{a.Item1} ";
                }));


            var dictionaryParameter = new EFCoreTaskParameter.DictionaryParameter
            {
                sql = result.sql,
                paramster = jObject
            };

            // 写入当前上下文
            if (EFCoreTaskParameter.dictionary.ContainsKey(result.context))
                EFCoreTaskParameter.dictionary[result.context].Add(dictionaryParameter);
            else
                EFCoreTaskParameter.dictionary.TryAdd(result.context, new List<EFCoreTaskParameter.DictionaryParameter>() { dictionaryParameter });

            return result;
        }

        /// <summary>
        /// 批量插入
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="context"></param>
        /// <param name="list"></param>
        public static void BulkAddedEntry<T>(this DbContext context, List<T> list)
            where T : class, new()
        {

            // 字段解析
            var mapping = EFCoreModel<T>.GetModelMapping(context);

            var table = EFCoreCommon.ListToDataTable<T>(list, mapping.tableName, mapping.rows);

            if (EFCoreTaskParameter.BulkTable.ContainsKey(context))
            {
                foreach (var item in EFCoreTaskParameter.BulkTable[context])
                {
                    if (item.TableName == table.TableName)
                    {
                        item.Merge(table, false, MissingSchemaAction.AddWithKey);
                        return;
                    }
                }

                EFCoreTaskParameter.BulkTable[context].Add(table);
            }
            else
                EFCoreTaskParameter.BulkTable.TryAdd(context, new List<DataTable> { table });
        }



        #endregion








        #region Select

        /// <summary>
        /// 查询条件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="where"></param>
        /// <returns></returns>
        public static ISelectResult<T> Where<T>(this ISelectResult<T> model, Expression<Func<T, bool>> where)
            where T : class, new()
        {
            var entity = model as SelectResult<T>;
            entity.where = where;
            return entity;
        }

        /// <summary>
        /// 排序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="keyasc"></param>
        /// <returns></returns>
        public static ISelectResult<T> OrderBy<T>(this ISelectResult<T> model, Expression<Func<T, object>> keyasc)
            where T : class, new()
        {
            var entity = model as SelectResult<T>;
            entity.keyasc = keyasc;
            return entity;
        }

        /// <summary>
        /// 排序 逆序
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="keyasc"></param>
        /// <returns></returns>
        public static ISelectResult<T> OrderByDescending<T>(this ISelectResult<T> model, Expression<Func<T, object>> keydesc)
            where T : class, new()
        {
            var entity = model as SelectResult<T>;
            entity.keydesc = keydesc;
            return entity;
        }

        /// <summary>
        /// 查询对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="Selector"></param>
        /// <returns></returns>
        public static ISelectResult<T> Select<T>(this ISelectResult<T> model, Expression<Func<T, object>> Selector)
            where T : class, new()
        {
            var entity = model as SelectResult<T>;
            entity.selector = Selector;
            return entity;
        }

        /// <summary>
        /// 跳过数据条数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="skip"></param>
        /// <returns></returns>
        public static ISelectResult<T> Skip<T>(this ISelectResult<T> model, int skip)
            where T : class, new()
        {
            var entity = model as SelectResult<T>;
            entity.Skip = skip;
            return entity;
        }

        /// <summary>
        /// 查询数据条数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="take"></param>
        /// <returns></returns>
        public static ISelectResult<T> Take<T>(this ISelectResult<T> model, int take)
            where T : class, new()
        {
            var entity = model as SelectResult<T>;
            entity.Take = take;
            return entity;
        }



        /// <summary>
        /// 使用缓存
        /// 指定缓存方式
        /// [默认缓存时间 10s]
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="iCache">自定义缓存实例</param>
        /// <param name="sliding">是否滑动过期 [如果在过期时间内有操作，则以当前时间点延长过期时间]</param>
        /// <param name="refresh">强制刷新数据</param>
        /// <returns></returns>
        public static ISelectResult<T> UseCache<T>(
            this IQueryable<T> source,
            bool refresh = false,
            bool sliding = false,
            IEntityFrameWorkCacheService iCache = null
            )
            where T : class, new()
        {
            return new SelectResult<T> { Model = source, TimeSpan = TimeSpan.FromSeconds(10), ICache = iCache, refresh = refresh, sliding = sliding };
        }


        /// <summary>
        /// 使用缓存
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <param name="iCache">自定义缓存实例</param>
        /// <param name="sliding">是否滑动过期 [如果在过期时间内有操作，则以当前时间点延长过期时间]</param>
        /// <param name="refresh">强制刷新数据</param>
        /// <returns></returns>
        public static ISelectResult<TSource> UseCache<TSource>(
            this IQueryable<TSource> source,
            TimeSpan timeSpan,
            bool refresh = false,
            bool sliding = false,
            IEntityFrameWorkCacheService iCache = null
        )
            where TSource : class, new()
        {
            return new SelectResult<TSource> { Model = source, TimeSpan = timeSpan, ICache = iCache, refresh = refresh, sliding = sliding };
        }


        private static ISelectResult<T> SelectWhereInit<T>(this ISelectResult<T> model) where T : class, new()
        {
            var entity = model as SelectResult<T>;

            if (entity.Take >= 0)
                entity.Model.Take(Convert.ToInt32(entity.Take));

            if (entity.Skip >= 0)
                entity.Model.Skip(Convert.ToInt32(entity.Skip));

            if (entity.selector != null)
                entity.Model.Select(entity.selector);

            if (entity.keyasc != null)
                entity.Model.OrderBy(entity.keyasc);

            if (entity.keydesc != null)
                entity.Model.OrderByDescending(entity.keydesc);

            if (entity.where != null)
                entity.Model.Where(entity.where);

            return entity;
        }

        public static IEnumerable<T> ToList<T>(this ISelectResult<T> model)
            where T : class, new()
        {
            return model.ToList(out bool isCache);
        }

        /// <summary>
        /// 查询数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <returns></returns>
        public static IEnumerable<T> ToList<T>(this ISelectResult<T> model, out bool isCache)
            where T : class, new()
        {

            var entity = model as SelectResult<T>;

            IEntityFrameWorkCacheService cache =
                entity.ICache == null ? EFCoreCommon.DefaultCacheService : entity.ICache;

            Tuple<bool, IEnumerable<T>> tuple = null;


            if (entity.refresh)
            {
                cache.Remove(entity.cacheKey);
                tuple = new Tuple<bool, IEnumerable<T>>(false, null);
            }
            else
            {
                tuple = cache.Exists<IEnumerable<T>>(entity.cacheKey);
            }


            if (tuple.Item1)
            {
                isCache = true;
                if (tuple.Item2 == null)
                {
                    isCache = false;

                    cache.Remove(entity.cacheKey);

                    var result = entity.Model.AsNoTracking().ToList();

                    cache.Add(entity.cacheKey, result, (TimeSpan)entity.TimeSpan, entity.sliding);

                    return result;
                }

                return tuple.Item2;
            }
            else
            {
                entity = entity.SelectWhereInit() as SelectResult<T>;

                var result = entity.Model.AsNoTracking().ToList();

                cache.Add(entity.cacheKey, result, (TimeSpan)entity.TimeSpan, entity.sliding);

                isCache = false;

                return result;
            }
        }

        public static T FirstOrDefault<T>(this ISelectResult<T> model)
            where T : class, new()
        {
            return model.FirstOrDefault(out bool isCache);
        }

        /// <summary>
        /// 查询数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <returns></returns>
        public static T FirstOrDefault<T>(this ISelectResult<T> model, out bool isCache)
            where T : class, new()
        {

            var entity = model as SelectResult<T>;

            IEntityFrameWorkCacheService cache =
                entity.ICache == null ? EFCoreCommon.DefaultCacheService : entity.ICache;

            Tuple<bool, T> tuple = null;


            if (entity.refresh)
            {
                cache.Remove(entity.cacheKey);
                tuple = new Tuple<bool, T>(false, null);
            }
            else
            {
                tuple = cache.Exists<T>(entity.cacheKey);
            }

            if (tuple.Item1)
            {
                isCache = true;

                if (tuple.Item2 == null)
                {
                    cache.Remove(entity.cacheKey);

                    isCache = false;

                    var result = entity.Model.AsNoTracking().FirstOrDefault();

                    cache.Add(entity.cacheKey, result, (TimeSpan)entity.TimeSpan, entity.sliding);

                    return result;
                }

                return tuple.Item2;
            }
            else
            {
                entity = entity.SelectWhereInit() as SelectResult<T>;

                var result = entity.Model.FirstOrDefault();

                cache.Add(entity.cacheKey, result, (TimeSpan)entity.TimeSpan, entity.sliding);

                isCache = true;

                return result;
            }
        }

        #endregion

        #region Update

        /// <summary>
        /// 更新忽略UNLL字段
        /// </summary>
        /// <param name="context">Context.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        public static void ExcludeNullField<T>(this DbContext context, T model) where T : class
        {
            foreach (PropertyInfo p in model.GetType().GetProperties())
            {
                var isNull = p.GetValue(model) != null;
                try
                {
                    context.Entry<T>(model).Property(p.Name).IsModified = isNull;
                }
                catch
                {
                }
            }
        }

        /// <summary>
        /// 更新数据条件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public static void Where<T>(this IResult<T> iResult, Expression<Func<T, bool>> where)
            where T : class, new()
        {
            if (iResult is UpdateResult<T>)
            {
                if (EFCoreTaskParameter.dictionary.ContainsKey(iResult.context))
                {
                    var tuple = EFCoreCommon.GetWhereStr(iResult.context, where);
                    //if (iResult.Fields.Count != 0)
                    //{

                    var param = EFCoreTaskParameter.dictionary[iResult.context]
                        .Where(a => a.sql == iResult.sql)
                        .First();

                    EFCoreTaskParameter.dictionary[iResult.context].RemoveAll(a => a.sql == iResult.sql);


                    var model = new EFCoreTaskParameter.DictionaryParameter
                    {
                        sql = $"{iResult.sql} where {tuple.Item2}",
                        paramster = param.paramster
                    };

                    EFCoreTaskParameter.dictionary[iResult.context].Add(model);


                    var sqllist = EFCoreTaskParameter.dictionary[iResult.context];

                    for (int i = 0; i < sqllist.Count; i++)
                        if (sqllist[i].sql.Trim() == iResult.sql.Trim())
                            sqllist.Remove(sqllist[i]);
                    //}
                }
                else
                {
                    throw new Exception("未找到当前上下文");
                }

            }
        }

        /// <summary>
        /// 更新数据源
        /// </summary>
        /// <returns></returns>
        public static IResult<T> BulkUpdate<T>(this DbSet<T> model, T entity)
            where T : class, new()
        {
            if (entity == null)
                return new UpdateResult<T>();

            var context = (DbContext)EFCoreCommon.GetValueByField(model.Local, "_context");

            return context.BulkModifiedEntry(entity);
        }

        /// <summary>
        /// 更新数据源
        /// </summary>
        /// <returns></returns>
        public static IResult<T> BulkUpdate<T>(this DbSet<T> model, params string[] row)
            where T : class, new()
        {
            if (row.Length < 1)
                return new UpdateResult<T>();


            var context = (DbContext)EFCoreCommon.GetValueByField(model.Local, "_context");

            // 字段解析
            var mapping = EFCoreModel<T>.GetModelMapping(context);

            // 构造返回值
            var result = new UpdateResult<T> { context = context };

            // 构造 操作数据
            result.sql =
                $"update {mapping.tableName} set {string.Join(',', row)} ";


            var dictionaryParameter = new EFCoreTaskParameter.DictionaryParameter
            {
                sql = result.sql,
                paramster = null
            };

            // 写入当前上下文
            if (EFCoreTaskParameter.dictionary.ContainsKey(result.context))
                EFCoreTaskParameter.dictionary[result.context].Add(dictionaryParameter);
            else
                EFCoreTaskParameter.dictionary.TryAdd(result.context, new List<EFCoreTaskParameter.DictionaryParameter>() { dictionaryParameter });

            return result;
        }

        #endregion

        #region Insert

        /// <summary>
        /// 批量插入
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="table"></param>
        /// <param name="where"></param>
        public static void BulkInsert<T>(this DbSet<T> model, List<T> list)
            where T : class, new()
        {

            var context = (DbContext)EFCoreCommon.GetValueByField(model.Local, "_context");

            context.BulkAddedEntry(list);

        }

        #endregion

        #region Delete

        /// <summary>
        /// 根据条件删除
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="table"></param>
        /// <param name="where"></param>
        public static void Delete<T>(this DbSet<T> model, Expression<Func<T, bool>> where)
            where T : class, new()
        {

            var context = (DbContext)EFCoreCommon.GetValueByField(model.Local, "_context");

            context.BulkDeleteEntry(where);
        }

        #endregion

        #region Submit

        /// <summary>
        /// 扩展提交
        /// </summary>
        /// <param name="context"></param>
        /// <param name="error">返回错误信息</param>
        /// <param name="transaction">是否启用事物</param>
        /// <returns></returns>
        public static int BeginSaveChanges(this DbContext context, out string error, bool transaction = true)
        {
            EFCoreTaskParameter.dictionary.TryRemove(context, out List<EFCoreTaskParameter.DictionaryParameter> list);
            EFCoreTaskParameter.BulkTable.TryRemove(context, out List<DataTable> tables);
            error = string.Empty;



            Func<SqlConnection, SqlTransaction, int> execute = (connection, tran) => 0;



            if (list != null && list.Count > 0)
            {

                execute += (connection, tran) =>
                {
                    Console.WriteLine(string.Join(';', list));

                    int row = 0;

                    foreach (var item in list)
                    {
                        if (item.paramster == null)
                        {
                            // 3.0+
                            // FormattableString str = $"{item.sql}";
                            // row += context.Database.ExecuteSqlInterpolated(str);

                            row += context.Database.ExecuteSqlCommand(item.sql);
                        }
                        else
                        {

                            IList<SqlParameter> parameters = new List<SqlParameter>();

                            foreach (var jitem in (JObject)item.paramster)
                            {
                                parameters.Add(new SqlParameter($"@{jitem.Key}", jitem.Value.ToString()));
                            }

                            row += context.Database.ExecuteSqlCommand(item.sql, parameters);
                        }
                    }


                    row += context.SaveChanges();
                    return row;
                };
            }
            else
            {
                execute += (connection, tran) =>
                {
                    return context.SaveChanges();
                };
            }



            if (tables != null && tables.Count > 0)
            {
                execute += (connection, tran) =>
                {
                    foreach (var item in tables)
                    {
                        using (var bulkCopy = transaction
                            ? new SqlBulkCopy(connection, SqlBulkCopyOptions.CheckConstraints, tran)
                            : new SqlBulkCopy(connection))
                        {
                            bulkCopy.DestinationTableName = item.TableName;
                            bulkCopy.BulkCopyTimeout = 0;
                            bulkCopy.WriteToServer(item);
                        }
                    }

                    return 1;
                };
            }





            if (list?.Count > 0 || tables?.Count > 0)
            {
                int rows = 0;
                if (transaction)
                    using (var dbTran = context.Database.BeginTransaction())
                    {
                        try
                        {
                            rows = execute.Invoke(
                                (SqlConnection)context.Database.GetDbConnection(),
                                (SqlTransaction)dbTran.GetDbTransaction());

                            dbTran.Commit();
                        }
                        catch (Exception ex)
                        {
                            dbTran.Rollback();
                            error = ex.Message;
                        }
                    }
                else
                    try
                    {
                        rows = execute.Invoke((SqlConnection)context.Database.GetDbConnection(), null);
                    }
                    catch (Exception ex)
                    {
                        rows = 0;
                        error = ex.Message;
                    }

                return rows;
            }

            return context.SaveChanges();
        }

        /// <summary>
        /// 扩展提交
        /// </summary>
        /// <param name="transaction">是否启用事物</param>
        public static int BeginSaveChanges(this DbContext context, bool transaction = true)
        {
            return context.BeginSaveChanges(out string msg, transaction);
        }

        #endregion
    }
}
